package com.px.framework.handler;

import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.core.lang.Validator;
import cn.hutool.http.HttpStatus;
import com.mysql.cj.jdbc.exceptions.CommunicationsException;
import com.px.common.core.domain.AjaxResult;
import com.px.common.exception.BaseException;
import com.px.common.exception.DemoModeException;
import com.px.common.exception.api.ApiException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.dao.DataAccessException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ConstraintViolationException;
import java.net.SocketTimeoutException;
import java.sql.SQLException;

/**
 * GlobalControllerAdviceHandler（全局异常处理器）
 *
 * @author 品讯科技
 */
@RestControllerAdvice
public class GlobalControllerAdviceHandler {

    private static final Logger log = LoggerFactory.getLogger(GlobalControllerAdviceHandler.class);

    /**
     * WebDataBinder是用来绑定请求参数到指定的属性编辑器，可以继承WebBindingInitializer
     * 来实现一个全部controller共享的dataBiner Java代码
     *
     * @param binder
     * @see <a href="https://developer.aliyun.com/article/871613">...</a>
     */
    @InitBinder
    public void dataBind(WebDataBinder binder) {
        // 可以实现「Get」方法时参数去除空格
        // https://docs.spring.io/spring-framework/docs/current/reference/html/core.html?spm=a2c6h.12873639.article-detail.6.5da832a6f0GzT3#beans-beans
        binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
    }

    // ----------------------- 自定义 Exception -----------------------

    /**
     * 基础异常
     */
    @ExceptionHandler(BaseException.class)
    public AjaxResult baseException(BaseException e) {
        return AjaxResult.build(HttpStatus.HTTP_INTERNAL_ERROR, e.getMessage());
    }

    /**
     * 业务接口异常
     */
    @ExceptionHandler(ApiException.class)
    public AjaxResult customException(ApiException e) {
        if (Validator.isNull(e.getCode())) {
            return AjaxResult.build(HttpStatus.HTTP_BAD_REQUEST, e.getMessage());
        }
        return AjaxResult.build(e.getCode(), e.getMessage());
    }

    /**
     * 演示模式异常
     */
    @ExceptionHandler(DemoModeException.class)
    public AjaxResult demoModeException(DemoModeException e) {
        return AjaxResult.build(HttpStatus.HTTP_INTERNAL_ERROR, "演示模式，不允许操作");
    }

    // ----------------------- JDK Exception -----------------------

    @ExceptionHandler(NullPointerException.class)
    public AjaxResult handleNullPointerException(NullPointerException e) {
        log.error(e.getMessage(), e);
        return AjaxResult.build(HttpStatus.HTTP_INTERNAL_ERROR, "空数据错误");
    }

    @ExceptionHandler(SocketTimeoutException.class)
    public AjaxResult handleSocketTimeoutException(SocketTimeoutException e) {
        log.error(e.getMessage(), e);
        return AjaxResult.build(HttpStatus.HTTP_INTERNAL_ERROR, "连接超时");
    }

    // ----------------------- SQL Exception -----------------------

    @ExceptionHandler(SQLException.class)
    public AjaxResult sqlException(SQLException e) {
        log.error(e.getMessage(), e);
        return AjaxResult.build(HttpStatus.HTTP_INTERNAL_ERROR, "SQL异常");
    }

    @ExceptionHandler(CommunicationsException.class)
    public AjaxResult handleCommunicationsException(CommunicationsException e) {
        log.error(e.getMessage(), e);
        return AjaxResult.build(HttpStatus.HTTP_INTERNAL_ERROR, "数据连接异常");
    }

    // ----------------------- Spring Exception -----------------------
    // URL路径不存在
    @ExceptionHandler(NoHandlerFoundException.class)
    public AjaxResult handlerNoFoundException(NoHandlerFoundException e) {
        log.error(e.getMessage(), e);
        return AjaxResult.build(HttpStatus.HTTP_NOT_FOUND, "URL路径不存在，请检查路径是否正确");
    }

    // 没有权限
    @ExceptionHandler(AccessDeniedException.class)
    public AjaxResult handleAuthorizationException(AccessDeniedException e) {
        log.error(e.getMessage());
        return AjaxResult.build(HttpStatus.HTTP_FORBIDDEN, "没有权限，请联系管理员授权");
    }

    // 用户登陆失效
    @ExceptionHandler(AccountExpiredException.class)
    public AjaxResult handleAccountExpiredException(AccountExpiredException e) {
        log.error(e.getMessage(), e);
        return AjaxResult.build(HttpStatus.HTTP_UNAUTHORIZED, "登陆失效：" + e.getMessage());
    }

    // 用户未找到
    @ExceptionHandler(UsernameNotFoundException.class)
    public AjaxResult handleUsernameNotFoundException(UsernameNotFoundException e) {
        log.error(e.getMessage(), e);
        return AjaxResult.build(HttpStatus.HTTP_UNAUTHORIZED, "用户不存：" + e.getMessage());
    }

    // 自定义验证异常
    @ExceptionHandler(BindException.class)
    public AjaxResult validatedBindException(BindException e) {
        log.error(e.getMessage(), e);
        String message = e.getAllErrors().get(0).getDefaultMessage();
        return AjaxResult.build(HttpStatus.HTTP_BAD_REQUEST, message);
    }

    // 自定义验证异常
    @ExceptionHandler(ConstraintViolationException.class)
    public AjaxResult constraintViolationException(ConstraintViolationException e) {
        log.error(e.getMessage(), e);
        String message = e.getConstraintViolations().iterator().next().getMessage();
        return AjaxResult.build(HttpStatus.HTTP_BAD_REQUEST, message);
    }

    // 自定义验证异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object validExceptionHandler(MethodArgumentNotValidException e) {
        log.error(e.getMessage(), e);
        String message;
        try {
            // final BindingResult bindingResult = e.getBindingResult();
            // final List<FieldError> fieldErrors = bindingResult.getFieldErrors();
            // message = e.getBindingResult().getFieldError().getDefaultMessage();
            final FieldError fieldError = e.getBindingResult().getFieldError();
            message = fieldError.getField() + fieldError.getDefaultMessage();
        } catch (Exception e1) {
            message = e.getMessage();
        }
        return AjaxResult.build(HttpStatus.HTTP_BAD_REQUEST, message);
    }

    // 数据访问异常
    @ExceptionHandler(DataAccessException.class)
    public AjaxResult handleAuthorizationException(DataAccessException e) {
        log.error(e.getMessage());
        return AjaxResult.build(HttpStatus.HTTP_INTERNAL_ERROR, "数据访问错误");
    }

    // ----------------------- hutool Exception -----------------------

    // 数据验证错误
    @ExceptionHandler(ValidateException.class)
    public AjaxResult handleValidateException(ValidateException e) {
        log.error(e.getMessage());
        return AjaxResult.build(HttpStatus.HTTP_BAD_REQUEST, e.getMessage());
    }

    // ----------------------- Exception -----------------------

    @ExceptionHandler(Exception.class)
    public AjaxResult handleException(Exception e) {
        log.error(e.getMessage(), e);
        return AjaxResult.build(HttpStatus.HTTP_INTERNAL_ERROR, e.getMessage());
    }

}
